// Copyright (C) Kumo inc. and its affiliates.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//


#pragma once

#include <functional>
#include <map>
#include <shared_mutex>
#include <unordered_map>
#include <melon/container/f14_map.h>
#include "melon/conv.h"
#include <pollux/common/base/exceptions.h>

namespace kumo::pollux::config {
    enum class CapacityUnit {
        BYTE,
        KILOBYTE,
        MEGABYTE,
        GIGABYTE,
        TERABYTE,
        PETABYTE
    };

    double toBytesPerCapacityUnit(CapacityUnit unit);

    CapacityUnit valueOfCapacityUnit(const std::string &unitStr);

    /// Convert capacity string with unit to the capacity number in the specified
    /// units
    uint64_t toCapacity(const std::string &from, CapacityUnit to);

    std::chrono::duration<double> toDuration(const std::string &str);

    /// The concrete config class should inherit the config base and define all the
    /// entries.
    class ConfigBase {
    public:
        template<typename T>
        struct Entry {
            Entry(
                const std::string &_key,
                const T &_val,
                std::function<std::string(const T &)> _toStr =
                        [](const T &val) { return melon::to<std::string>(val); },
                std::function<T(const std::string &, const std::string &)> _toT =
                        [](const std::string &k, const std::string &v) {
                    auto converted = melon::tryTo<T>(v);
                    POLLUX_CHECK(
                        converted.hasValue(),
                        fmt::format(
                            "Invalid configuration for key '{}'. Value '{}' cannot be converted to type {}.",
                            k,
                            v,
                            melon::demangle(typeid(T))));
                    return converted.value();
                })
                : key{_key}, defaultVal{_val}, toStr{_toStr}, toT{_toT} {
            }

            const std::string key;
            const T defaultVal;
            const std::function<std::string(const T &)> toStr;
            const std::function<T(const std::string &, const std::string &)> toT;
        };

        ConfigBase(
            std::unordered_map<std::string, std::string> &&configs,
            bool _mutable = false)
            : configs_(std::move(configs)), mutable_(_mutable) {
        }

        virtual ~ConfigBase() {
        }

        template<typename T>
        ConfigBase &set(const Entry<T> &entry, const T &val) {
            POLLUX_CHECK(mutable_, "Cannot set in immutable config");
            std::unique_lock<std::shared_mutex> l(mutex_);
            configs_[entry.key] = entry.toStr(val);
            return *this;
        }

        ConfigBase &set(const std::string &key, const std::string &val);

        template<typename T>
        ConfigBase &unset(const Entry<T> &entry) {
            POLLUX_CHECK(mutable_, "Cannot unset in immutable config");
            std::unique_lock<std::shared_mutex> l(mutex_);
            configs_.erase(entry.key);
            return *this;
        }

        ConfigBase &reset();

        template<typename T>
        T get(const Entry<T> &entry) const {
            std::shared_lock<std::shared_mutex> l(mutex_);
            auto iter = configs_.find(entry.key);
            return iter != configs_.end()
                       ? entry.toT(entry.key, iter->second)
                       : entry.defaultVal;
        }

        template<typename T>
        melon::Optional<T> get(
            const std::string &key,
            std::function<T(std::string, std::string)> toT = [](auto /* unused */,
                                                                auto value) {
                return melon::to<T>(value);
            }) const {
            auto val = get(key);
            if (val.hasValue()) {
                return toT(key, val.value());
            } else {
                return melon::none;
            }
        }

        template<typename T>
        T get(
            const std::string &key,
            const T &defaultValue,
            std::function<T(std::string, std::string)> toT = [](auto /* unused */,
                                                                auto value) {
                return melon::to<T>(value);
            }) const {
            auto val = get(key);
            if (val.hasValue()) {
                return toT(key, val.value());
            } else {
                return defaultValue;
            }
        }

        bool valueExists(const std::string &key) const;

        const std::unordered_map<std::string, std::string> &rawConfigs() const;

        std::unordered_map<std::string, std::string> rawConfigsCopy() const;

    protected:
        mutable std::shared_mutex mutex_;
        std::unordered_map<std::string, std::string> configs_;

    private:
        melon::Optional<std::string> get(const std::string &key) const;

        const bool mutable_;
    };
} // namespace kumo::pollux::config
